#include "aggAux.h"
#include "manager.h"

vector<pEntity> sortSeedNodes(pMesh fineMesh);

template<typename Container, typename Compare>
void print_heap(const Container&, Compare, const char* title = 0);
// Performs the control volumes agglomeration
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// IN: theMesh, setOfDomains, nMesh
// OUT: aggMesh
void aggCV(pMesh theMesh, set<int> &setOfDomains, vector<pMesh> aggMesh, int iMesh)
{

cout<<"   Creating Super Nodes... ";

// Definig the domain flag
set<int>::iterator iter = setOfDomains.begin();

int iSN(0); // Supernode counters

pMesh fineMesh = (iMesh == 0)?theMesh:aggMesh.at(iMesh - 1);

// (DEV) verificar se isso e sempre necessario
fineMesh->modifyState(0,1);	// create edge adjacency around nodes

// Ordering seed nodes (create priority queue) according to: 1.Boundaries, 2.Connectivities
vector<pEntity> nodePQ = sortSeedNodes(fineMesh);
// (DEV) Print Function (just for debug purposes)
//print_heap(nodePQ, CompareFMDB(),"Nodes heap before agglomeration: ");
// Loop on the seed priority queue for this level until queue is empty (no more seeds)
while(!nodePQ.empty())
{
    // Retrieve node from the list
    pEntity node  = nodePQ.at(0);

	int aggStatus(0);
	EN_getDataInt (node, MD_lookupMeshDataId("aggStatus"),&aggStatus);

	if (aggStatus == 1)
	{
		// Do nothing! Node already aggregated at this level
	}else
	{
		// Create Supernode based on the seed node!
		// Get ID and coordinates from seed node
        int ID = EN_id(node);
        //int ID = iSN; (may be also used as ID)
        double coord[3] = {.0, .0, .0};
        V_coord(node, coord);

        // Create new vertex in the coarse mesh
        pEntity seedNode = aggMesh.at(iMesh)->createVertex(ID, coord[0], coord[1], coord[2], 0);

	    vector<pEntity> neighbourNode;
 	    int numEdges = V_numEdges(node);        // get number of edges surrounding
        //cout<<"NumEdges: "<<numEdges<<"\n";                                        // the entity vertex
      	for (int i=0; i<numEdges; i++)          // loop over all edges using vertex
      	{
           	pEdge edge = V_edge(node,i);
           	if (node == E_vertex(edge, 0))
           	{
           		neighbourNode.push_back(E_vertex(edge, 1));
           	}else
           	{
           		neighbourNode.push_back(E_vertex(edge, 0));
      		}
      	}
      	char volume_string[64];
        // (DEV) -  VERIFICAR COMO OBTER gEntity!!
        pGEntity gEntity;
     	//sprintf(volume_string,"volume-%d",GEN_tag(gEntity));
     	sprintf(volume_string,"volume-%d",*iter);
      	double seedVolume = .0;
      	EN_getDataDbl(node,MD_lookupMeshDataId(volume_string),&seedVolume);

        int numberOfAggNodes(0); // Number of nodes agglomerated to seed node
      	// Loop on all connected nodes
     	for (int k(0); k<neighbourNode.size(); k++)
     	{
     		aggStatus = 0;
     		EN_getDataInt (neighbourNode.at(k), MD_lookupMeshDataId("aggStatus"), &aggStatus);
     		if (aggStatus == 1)
     		{
     			// Do nothing! Neighbour node already aggregated at this level
     		}else
     		{
     			// (DEV) -  VERIFICAR COMO OBTER gEntity!!
     			char volume_string_2[64];
     			//sprintf(volume_string_2,"volume-%d",GEN_tag(gEntity));
     			sprintf(volume_string_2,"volume-%d",*iter);
     			double neighbourVolume = .0;

			EN_getDataDbl(neighbourNode.at(k),MD_lookupMeshDataId(volume_string_2),&neighbourVolume);

			// Adding volume to seed node
			seedVolume += neighbourVolume;

			// Changing status of neighbour node to already aggregated
            aggStatus = 1;
            EN_attachDataInt(neighbourNode.at(k), MD_lookupMeshDataId("aggStatus"), aggStatus);
            EN_attachDataInt(neighbourNode.at(k), MD_lookupMeshDataId("SeedID"), ID);

            // Loop on neighbours of neighbours in order to update the priority list
            vector<pEntity> neighbourOfNeighbour;
	        int numEdgesNeigh = V_numEdges(neighbourNode.at(k)); // get number of edges surrounding
                                                					 // the entity vertex
      		for (int m=0; m<numEdgesNeigh; m++)         		 // loop over all edges using vertex
            {
           		pEdge edgeNeigh = V_edge(neighbourNode.at(k),m);
           		if (neighbourNode.at(k) == E_vertex(edgeNeigh, 0))
           		{
           			neighbourOfNeighbour.push_back(E_vertex(edgeNeigh, 1));
           		}else
           		{
           			neighbourOfNeighbour.push_back(E_vertex(edgeNeigh, 0));
      			}
      			}
      		/* (DEV) Dynamic priority queue!!
      			for (int n(0); n<neighbourOfNeighbour.size(); n++)
      			{
      				// Getting item relative to this node in the priority queue
      				int pqItemLocation(0);
      				EN_getDataInt (neighbourOfNeighbour.at(n), MD_lookupMeshDataId("pqItemLocation"), &pqItemLocation);
      				pq_item it = pqItemList.at(pqItemLocation);

      				// Reducing its priority by one (adding one, as we use the negative
      				// of the number of connectivities)
      				int priority(0);
      				EN_getDataInt (neighbourOfNeighbour.at(n), MD_lookupMeshDataId("priority"), &priority);
      				int newPriority = priority + 1;
      				PQ.del_item(it);
      				pq_item itNew = PQ.insert(newPriority, neighbourOfNeighbour.at(n));

      				// Changing item in the list for future searches
      				pqItemList.at(pqItemLocation) = itNew;
      			}
                 */
                 numberOfAggNodes++;
     		}
     	}
     	// Update of the seed node, using all the data from all agglomerated nodes
        EN_attachDataDbl(seedNode, MD_lookupMeshDataId(volume_string), seedVolume);
        EN_attachDataInt(node, MD_lookupMeshDataId("SeedID"), ID);
        if (numberOfAggNodes > 0) EN_attachDataInt(seedNode, MD_lookupMeshDataId("aggStatus"), 1);

        int isBoundary(0);
        EN_getDataInt(node, MD_lookupMeshDataId("isBoundary"), &isBoundary);
        EN_attachDataInt(seedNode, MD_lookupMeshDataId("isBoundary"), isBoundary);
     	// Update counter (may be also used as ID)
     	iSN++;
	}
    // Update heap
    pop_heap(nodePQ.begin(), nodePQ.end(), CompareFMDB());
    nodePQ.pop_back();
}
// (DEV) Printing heap (just for debug purposes)
//print_heap(nodePQ, CompareFMDB(),"Nodes heap after agglomeration: ");
// Printing the number of nodes of the coarse mesh
cout << iSN <<" nodes in the "<< iMesh <<"° coarse mesh \n";
}

template<typename Container, typename Compare>
void print_heap(const Container& container, Compare comp, const char* title = 0)
{
    if (title)
        std::cout << title << std::endl;

    std::vector<typename Container::value_type> temp(container.begin(),
            container.end());
    while (temp.size()) {
        double coord[3] = {.0, .0, .0};
        int priority = 0;

        V_coord(temp[0], coord);
        EN_getDataInt(temp[0], MD_lookupMeshDataId("priority"), &priority);

        std::cout<<"ID: "<<EN_id(temp[0])<<" | Priority: "<< priority <<" | x: "<<coord[0]<<" | y: "<<coord[1]<<" | z: "<<coord[2]<<std::endl;

        std::pop_heap(temp.begin(), temp.end(), comp);
        temp.pop_back();
    }
}
